package com.kylin.domain.oauth2.social.jwt;

import com.kylin.shared.utils.ApplicationUtils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

/**
 * Jwt Token功能处理类
 * @author Damon S.
 * @version v1.0.1
 * @date 2020年07月27日 23:21
 */
@Slf4j
@Component
@RequiredArgsConstructor
public final class JwtTokenProvider {

    private final JwtSecurityProperties jwtSecurityProperties;

    private final SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);


    public Boolean validateToken(String token, String username) {
        final String subject = getUsernameFromToken(token);
        return !StringUtils.isEmpty(subject)
                && subject.equals(username)
                && !isTokenExpired(token);
    }

    public Date getExpiresDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    private boolean isTokenExpired(String token) {
        final Date expiresAt = getExpiresDateFromToken(token);
        return expiresAt.before(Date.from(Instant.now()));
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        Claims claims;
        try {
            claims = Jwts.parserBuilder()
                    .setAllowedClockSkewSeconds(3 * 60)
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (MalformedJwtException e) {
            log.error("Invalid JWT signature.token=[{}]", token);
            throw e;
        } catch (ExpiredJwtException e) {
            log.error("Expired JWT token.token=[{}]", token);
            throw e;
        } catch (UnsupportedJwtException e) {
            log.error("Unsupported JWT token.token=[{}]", token);
            throw e;
        } catch (IllegalArgumentException e) {
            log.error("JWT token compact of handler are invalid.token=[{}]", token);
            throw e;
        }
        return claimsResolver.apply(claims);
    }

    public String createToken(Map<String, Object> claims, String subject) {
        final Date createdAt = Date.from(Instant.now());
        final Date expiresAt = new Date(createdAt.getTime() + jwtSecurityProperties.getExpiresIn());

        final JwtBuilder builder = Jwts.builder()
                .compressWith(CompressionCodecs.DEFLATE)
                .setClaims(claims)
                .setId(ApplicationUtils.randomUUID())
                .setSubject(subject)
                .setIssuedAt(createdAt)
                .setExpiration(expiresAt)
                .setNotBefore(createdAt)
                .signWith(secretKey);

        Optional.ofNullable(jwtSecurityProperties.getIssuer())
                .ifPresent(builder::setIssuer);
        Optional.ofNullable(jwtSecurityProperties.getAudience())
                .ifPresent(builder::setAudience);
        return builder.compact();
    }
}
